#include "tty.h"
#include "dev.h"
#include "console.h"
#include "kbd.h"
#include "log.h"
#include "irq.h"

static tty_t   tty_devs[TTY_NR];
static uint8_t curr_tty_idx = 0;

static tty_t *get_tty(device_t *dev)
{
    int tty = dev->minor;    // use the minor to store tty id;
    if ((tty < 0) || (tty >= TTY_NR) || (!dev->open_count))
    {
        return (tty_t *)0;
    }
    return tty_devs + tty;
}

int tty_filo_put(tty_fifo_t *fifo, char c)
{
    irq_state_t irq_state = irq_enter_protect();
    if (fifo->count >= fifo->size)
    {
        irq_exit_protect(irq_state);
        return -1;
    }
    fifo->buf[fifo->write++] = c;
    if (fifo->write >= fifo->size)
    {
        fifo->write = 0;
    }
    fifo->count++;
    irq_exit_protect(irq_state);
    return 0;
}
int tty_fifo_get(tty_fifo_t *fifo, char *c)
{
    irq_state_t irq_state = irq_enter_protect();
    if (fifo->count <= 0)
    {
        irq_exit_protect(irq_state);
        return -1;
    }
    *c = fifo->buf[fifo->read++];
    if (fifo->read >= fifo->size)
    {
        fifo->read = 0;
    }
    fifo->count--;
    if (fifo->count < 0)
    {
        fifo->count = 0;
    }
    irq_exit_protect(irq_state);
    return 0;
}

static void tty_fifo_init(tty_fifo_t *fifo, char *buf, int size)
{
    fifo->buf   = buf;
    fifo->count = 0;
    fifo->read = fifo->write = 0;
    fifo->size               = size;
}

int tty_open(device_t *dev)
{
    int idx = dev->minor;
    if (idx < 0 || (idx >= TTY_NR))
    {
        log_printf("open tty failed. incorrect tty num =%d", idx);
        return -1;
    }
    tty_t *tty = tty_devs + idx;
    tty_fifo_init(&tty->ofifo, tty->obuf, TTY_OBUF_SIZE);
    sem_init(&tty->osem, TTY_OBUF_SIZE);
    tty->oflags = TTY_OCRLF;

    tty_fifo_init(&tty->ififo, tty->ibuf, TTY_IBUF_SIZE);
    sem_init(&tty->isem, 0);
    tty->iflags = TTY_IECHO | TTY_ICRLF;

    tty->console_idx = idx;
    kbd_init();
    console_init(idx);
    return 0;
}
int tty_write(device_t *dev, int addr, char *buf, int size);
int tty_read(device_t *dev, int addr, char *buf, int size)
{
    if (size < 0)
    {
        return -1;
    }
    tty_t *tty = get_tty(dev);
    if (!tty)
    {
        return -1;
    }
    char *pbuf = buf;
    int   len  = 0;
    while (len < size)
    {
        sem_wait(&tty->isem);
        char ch;
        tty_fifo_get(&tty->ififo, &ch);
        switch (ch)
        {
        case 0x7f:
            if (len == 0)
            {
                continue;
            }
            len--;
            pbuf--;
            break;
        case '\n':
            if ((tty->iflags & TTY_ICRLF) && (len < size - 1))
            {
                *pbuf++ = '\r';
                len++;
            }
            *pbuf++ = '\n';
            len++;
            break;
        default:
            *pbuf++ = ch;
            len++;
            break;
        }
        if (tty->iflags & TTY_IECHO)
        {
            tty_write(dev, 0, &ch, 1);
        }
        if ((ch == '\n') || (ch == '\r'))
        {
            break;
        }
    }
    return len;
}
int tty_write(device_t *dev, int addr, char *buf, int size)
{
    if (size < 0)
    {
        return -1;
    }
    tty_t *tty = get_tty(dev);
    if (!tty)
    {
        return -1;
    }
    int len = 0;
    while (size--)
    {
        sem_wait(&tty->osem);
        int err = tty_filo_put(&tty->ofifo, *buf++);
        if (err < 0)
        {
            break;
        }
        len++;
        // if(display is busy)
        //{
        // continue;
        // }else{
        // console_write_tty(tty);
        // }
        console_write_tty(tty);
    }
    return len;
}
int tty_control(device_t *dev, int cmd, int arg0, int arg1)
{
    tty_t *tty = get_tty(dev);
    switch (cmd)
    {
    case TTY_CMD_ECHO:
        if (arg0)
        {
            tty->iflags |= TTY_IECHO;
        }
        else
        {
            tty->iflags &= ~TTY_IECHO;
        }
        break;
    }
    return 0;
}
void tty_close(device_t *dev)
{
}

void tty_select(int console_idx)
{
    if (console_idx >= 0 && console_idx < TTY_NR && console_idx != curr_tty_idx)
    {
        console_select(console_idx);
        curr_tty_idx = console_idx;
    }
}
void tty_in(char ch)
{
    tty_t *tty = tty_devs + curr_tty_idx;
    if (!tty)
    {
        log_printf("tty_in console_idx error:idx:%d", curr_tty_idx);
        return;
    }
    if (sem_count(&tty->isem) >= TTY_IBUF_SIZE)
    {
        return;
    }
    tty_filo_put(&tty->ififo, ch);
    sem_notify(&tty->isem);
}

dev_desc_t dev_tty_desc = {
    .name    = "tty",
    .major   = DEV_TTY,
    .open    = tty_open,
    .read    = tty_read,
    .write   = tty_write,
    .control = tty_control,
    .close   = tty_close,
};